package com.hc.app.dto;

import com.hc.app.exception.FrequencyControlErrorEnum;
import com.hc.app.exception.FrequencyControlException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author: huangcheng
 * @CreateTime: 2024-07-25  14:50
 * @Description: 漏桶算法
 * @Version: 1.0
 */
@Data
@Slf4j
public class LeakBucketDTO extends FrequencyControlDto{
    /**
     * 令牌桶容量
     */
    private final long capacity;
    /**
     * 每秒漏掉的令牌数
     */
    private final double rate;
    /**
     * 当前请求数量
     */
    private double water;
    /**
     * 上次补充令牌的时间
     */
    private long lastRefillTime;

    private ReentrantLock lock = new ReentrantLock();

    /**
     * 构造初始容量
     */
    public LeakBucketDTO(long capacity, double rate) {
        if (capacity <= 0 || rate <= 0) {
            throw new FrequencyControlException(FrequencyControlErrorEnum.CAPACITY_REFILL_ERROR);
        }
        this.capacity = capacity;
        this.rate = rate;
        this.water = 0;
        this.lastRefillTime = System.nanoTime();
    }
    /**
     * 是否获取令牌
     * @param permits
     * @return
     */
    public boolean tryAcquire(int permits) {
        lock.lock();
        try {
            //消费令牌
            consumerTokens();
            // 如果补充的令牌少于需求就表示令牌不够
            if (capacity - water < permits) {
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }
    /**
     * 添加令牌
     * @param permits
     */
    public void deductionToken(int permits) {
        lock.lock();
        try {
            water += permits;
        } finally {
            lock.unlock();
        }
    }

    private void consumerTokens() {
        long currentTime = System.nanoTime();
        // 转换为秒
        double elapsedTime = (currentTime - lastRefillTime) / 1e9;
        double leakedWater = elapsedTime * rate;
        log.info("tokensToAdd is {}", leakedWater);
        water = Math.max(0,water - leakedWater);
        log.info("current tokens is {}", water);
        lastRefillTime = currentTime;
    }
}
